<script lang="tsx">
  import { computed, defineComponent, onMounted, PropType, ref, unref, watch, nextTick } from 'vue'
  import { ElCol, ElForm, ElFormItem, ElRow, ElTooltip } from 'element-plus'
  import { componentMapGet,componentMap } from '@/index'
  import { propTypes } from '@/utils/propTypes'
  import { getSlot } from '@/utils/tsxHelper'
  import {
    initModel,
    setComponentProps,
    setFormItemSlots,
    setGridProp,
    setItemComponentSlots,
    setTextPlaceholder
  } from './helper'
  import { useRenderSelect } from './components/useRenderSelect'
  import { useRenderRadio } from './components/useRenderRadio'
  import { useRenderCheckbox } from './components/useRenderCheckbox'
  import { useDesign } from '@/hooks/web/useDesign'
  import { findIndex } from '@/utils'
  import { set } from 'lodash-es'
  import { FormProps } from './types'
  import { Icon } from '@/components/Icon'
  import { FormSchema, FormSetPropsType } from '@/types/form'
  import { useI18n } from '@/hooks/web/useI18n'
  const { getPrefixCls } = useDesign()

  const prefixCls = getPrefixCls('form')

  // console.log(componentMap,'componentMap')

  const { t } = useI18n()

  export default defineComponent({
    // eslint-disable-next-line vue/no-reserved-component-names
    name: 'Form',
    props: {
      // 生成Form的布局结构数组
      schema: {
        type: Array as PropType<FormSchema[]>,
        default: () => []
      },
      // 是否需要栅格布局
      // update by 芋艿：将 true 改成 false，因为项目更常用这种方式
      isCol: propTypes.bool.def(false),
      // 栅格布局的间距
      gutter: propTypes.number.def(20),
      // 表单数据对象
      model: {
        type: Object as PropType<Recordable>,
        default: () => ({})
      },
      // 是否自动设置placeholder
      autoSetPlaceholder: propTypes.bool.def(true),
      // 是否自定义内容
      isCustom: propTypes.bool.def(false),
      // 表单label宽度
      labelWidth: propTypes.oneOfType([String, Number]).def('auto'),
      // 是否 loading 数据中 add by 芋艿
      vLoading: propTypes.bool.def(false)
    },
    emits: ['register'],
    setup(props, { slots, expose, emit }) {
      // element form 实例
      const elFormRef = ref<ComponentRef<typeof ElForm>>()
      // useForm传入的props
      const outsideProps = ref<FormProps>({})

      const mergeProps = ref<FormProps>({})

      const getProps = computed(() => {
        const propsObj = { ...props }
        Object.assign(propsObj, unref(mergeProps))
        return propsObj
      })

      // 表单数据
      const formModel = ref<Recordable>({})

      onMounted(() => {
        emit('register', unref(elFormRef)?.$parent, unref(elFormRef))
      })

      // 对表单赋值
      const setValues = (data: Recordable = {}) => {
        formModel.value = Object.assign(unref(formModel), data)
      }

      const setProps = (props: FormProps = {}) => {
        mergeProps.value = Object.assign(unref(mergeProps), props)
        outsideProps.value = props
      }

      const delSchema = (field: string) => {
        const { schema } = unref(getProps)

        const index = findIndex(schema, (v: FormSchema) => v.field === field)
        if (index > -1) {
          schema.splice(index, 1)
        }
      }

      const addSchema = (formSchema: FormSchema, index?: number) => {
        const { schema } = unref(getProps)
        if (index !== void 0) {
          schema.splice(index, 0, formSchema)
          return
        }
        schema.push(formSchema)
      }

      const setSchema = (schemaProps: FormSetPropsType[]) => {
        const { schema } = unref(getProps)
        for (const v of schema) {
          for (const item of schemaProps) {
            if (v.field === item.field) {
              set(v, item.path, item.value)
            }
          }
        }
      }

      const getElFormRef = (): ComponentRef<typeof ElForm> => {
        return unref(elFormRef) as ComponentRef<typeof ElForm>
      }

      expose({
        setValues,
        formModel,
        setProps,
        delSchema,
        addSchema,
        setSchema,
        getElFormRef
      })

      // 监听表单结构化数组，重新生成formModel
      watch(
        () => unref(getProps).schema,
        (schema = []) => {
          formModel.value = initModel(schema, unref(formModel))
          // 清除表单校验状态
          nextTick(() => {
            setTimeout(() => {
              elFormRef.value?.clearValidate()
            }, 0)
          })
        },
        {
          immediate: true,
          deep: true
        }
      )

      // 渲染包裹标签，是否使用栅格布局
      const renderWrap = () => {
        const { isCol, gutter = 20 } = unref(getProps)
        const content = isCol ? (
          <ElRow gutter={gutter} style={`width: calc(100% + ${gutter}px)`}>
            {renderFormItemWrap()}
          </ElRow>
        ) : (
          renderFormItemWrap()
        )
        return content
      }

      // 是否要渲染el-col
      const renderFormItemWrap = () => {
        // hidden属性表示隐藏，不做渲染
        const { schema = [], isCol } = unref(getProps)

        return schema
          .filter((v) => !v.hidden)
          .map((item) => {
            // 如果是 Divider 组件，需要自己占用一行
            const isDivider = item.component === 'Divider'
            const Com = componentMapGet('Divider') as ReturnType<typeof defineComponent>
            return isDivider ? (
              <Com {...{ contentPosition: 'left', ...item.componentProps }}>
                {t(item?.i18n || '') || item?.label || ''}
              </Com>
            ) : isCol ? (
              // 如果需要栅格，需要包裹 ElCol
              <ElCol {...setGridProp(item.colProps)}>{renderFormItem(item)}</ElCol>
            ) : (
              renderFormItem(item)
            )
          })
      }

      // 渲染formItem
      const renderFormItem = (item: FormSchema) => {
        // 单独给只有options属性的组件做判断
        const notRenderOptions = ['SelectV2', 'Cascader', 'Transfer']
        const slotsMap: Recordable = {
          ...setItemComponentSlots(slots, item?.componentProps?.slots, item.field)
        }
        if (
          item?.component !== 'SelectV2' &&
          item?.component !== 'Cascader' &&
          item?.componentProps?.options
        ) {
          slotsMap.default = () => renderOptions(item)
        }

        const formItemSlots: Recordable = setFormItemSlots(slots, item.field)
        // 如果有 labelMessage，自动使用插槽渲染
        if (item?.labelMessage) {
          formItemSlots.label = () => {
            return (
              <>
                <span>{t(item?.i18n || '') || item?.label || ''}</span>
                <ElTooltip placement="right" raw-content>
                  {{
                    content: () => (
                      <span
                        v-dompurify-html={
                          t(item?.i18nLabelMessage || '') || item.labelMessage || ''
                        }
                      ></span>
                    ),
                    default: () => (
                      <Icon
                        icon="ep:warning"
                        size={16}
                        color="var(--el-color-primary)"
                        class="relative top-1px ml-2px"
                      ></Icon>
                    )
                  }}
                </ElTooltip>
              </>
            )
          }
        }
        // 隐藏错误信息showMessage=false
        return (
          <ElFormItem
            {...(item.formItemProps || {})}
            prop={item.field}
            label={t(item?.i18n || '') || item.label || ''}
            showMessage={false}
          >
            {{
              ...formItemSlots,
              default: () => {
                const Com = componentMapGet(item.component as string) as ReturnType<
                  typeof defineComponent
                >

                const { autoSetPlaceholder } = unref(getProps)

                return slots[item.field] ? (
                  getSlot(slots, item.field, formModel.value)
                ) : (
                  <Com
                    vModel={formModel.value[item.field]}
                    {...(autoSetPlaceholder && setTextPlaceholder(item))}
                    {...setComponentProps(item)}
                    style={item.componentProps?.style}
                    {...(notRenderOptions.includes(item?.component as string) &&
                    item?.componentProps?.options
                      ? { options: item?.componentProps?.options || [] }
                      : {})}
                  >
                    {{ ...slotsMap }}
                  </Com>
                )
              }
            }}
          </ElFormItem>
        )
      }

      // 渲染options
      const renderOptions = (item: FormSchema) => {
        switch (item.component) {
          case 'Select':
          case 'SelectV2':
            const { renderSelectOptions } = useRenderSelect(slots)
            return renderSelectOptions(item)
          case 'RadioGroup':
          case 'RadioButton':
            const { renderRadioOptions } = useRenderRadio()
            return renderRadioOptions(item)
          case 'Checkbox':
          case 'CheckboxButton':
            const { renderCheckboxOptions } = useRenderCheckbox()
            return renderCheckboxOptions(item)
          default:
            break
        }
      }

      // 过滤传入Form组件的属性
      const getFormBindValue = () => {
        // 避免在标签上出现多余的属性
        const delKeys = ['schema', 'isCol', 'autoSetPlaceholder', 'isCustom', 'model']
        const props = { ...unref(getProps) }
        for (const key in props) {
          if (delKeys.indexOf(key) !== -1) {
            delete props[key]
          }
        }
        return props
      }

      return () => (
        <ElForm
          ref={elFormRef}
          {...getFormBindValue()}
          model={props.isCustom ? props.model : formModel}
          class={prefixCls}
          v-loading={props.vLoading}
        >
          {{
            // 如果需要自定义，就什么都不渲染，而是提供默认插槽
            default: () => {
              const { isCustom } = unref(getProps)
              return isCustom ? getSlot(slots, 'default') : renderWrap()
            }
          }}
        </ElForm>
      )
    }
  })
</script>

<style lang="scss" scoped>
  // .#{$elNamespace}-form.#{$namespace}-form .#{$elNamespace}-row {
  //   margin-right: 0 !important;
  //   margin-left: 0 !important;
  // }
</style>
